﻿namespace Mp3Observer.Common.Behaviors
{
    #region usings

    using System;
    using System.ComponentModel;
    using System.Windows;
    using System.Windows.Input;
    using System.Windows.Interactivity;

    #endregion

    /// <summary>
    /// The Triggeraction executes a Command.
    /// </summary>
    /// <example>
    /// <code>
    /// <![CDATA[ 
    /// <ListBox ItemsSource="{Binding Path=...}" ...>
    ///     <i:Interaction.Triggers>
    ///         <behaviours:RoutedEventTrigger RoutedEvent="KeyDown">
    ///             <behaviours:CommandAction Command="{Binding DoubleClickCommand}" />
    ///         </behaviours:RoutedEventTrigger>
    ///         <behaviours:RoutedEventTrigger RoutedEvent="PreviewMouseLeftButtonDown">
    ///             <behaviours:CommandAction Command="{Binding DoubleClickCommand}" />
    ///         </behaviours:RoutedEventTrigger>
    ///     </i:Interaction.Triggers>
    /// ]]>
    /// </code>
    /// </example>
    public class CommandAction : TargetedTriggerAction<FrameworkElement>, ICommandSource
    {
        #region DPs

        /// <summary>
        /// the CommandProperty as a dep.prop for Databinding.
        /// </summary>
        public static readonly DependencyProperty CommandProperty = DependencyProperty.Register("Command", typeof(ICommand), typeof(CommandAction), new PropertyMetadata((ICommand)null, OnCommandChanged));

        /// <summary>
        /// the CommandParameter as a dep.prop for Databinding.
        /// </summary>
        public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register("CommandParameter", typeof(object), typeof(CommandAction), new PropertyMetadata());

        /// <summary>
        /// the CommandTarget as a dep.prop for Databinding.
        /// </summary>
        public static readonly DependencyProperty CommandTargetProperty = DependencyProperty.Register("CommandTarget", typeof(IInputElement), typeof(CommandAction), new PropertyMetadata());

        /// <summary>
        /// the SyncOwnerIsEnabled as a dep.prop for Databinding.
        /// </summary>
        public static readonly DependencyProperty SyncOwnerIsEnabledProperty = DependencyProperty.Register("SyncOwnerIsEnabled", typeof(bool), typeof(CommandAction), new PropertyMetadata());

        #endregion

        #region Event Declaration

        /// <summary>
        /// can execute change event handler
        /// </summary>
        private EventHandler canExecuteChanged;

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the command that will be executed when the command source is invoked.
        /// </summary>
        /// <value></value>
        [Category("Command Properties")]
        public ICommand Command
        {
            get { return (ICommand)GetValue(CommandProperty); }
            set { SetValue(CommandProperty, value); }
        }

        /// <summary>
        /// Gets or sets the a user defined data value that can be passed to the command when it is executed.
        /// </summary>
        /// <value></value>
        /// <returns>
        /// The command specific data.
        /// </returns>
        [Category("Command Properties")]
        public object CommandParameter
        {
            get { return (object)GetValue(CommandParameterProperty); }
            set { SetValue(CommandParameterProperty, value); }
        }

        /// <summary>
        /// Gets or sets the object that the command is being executed on.
        /// </summary>
        /// <value></value>
        [Category("Command Properties")]
        public IInputElement CommandTarget
        {
            get { return (IInputElement)GetValue(CommandTargetProperty); }
            set { SetValue(CommandTargetProperty, value); }
        }

        /// <summary>
        /// Gets or sets a value indicating whether [sync owner is enabled].
        /// </summary>
        /// <value><c>true</c> if [sync owner is enabled]; otherwise, <c>false</c>.</value>
        [Category("Command Properties")]
        public bool SyncOwnerIsEnabled
        {
            get { return (bool)GetValue(SyncOwnerIsEnabledProperty); }
            set { SetValue(SyncOwnerIsEnabledProperty, value); }
        }

        #endregion

        #region Overrides

        /// <summary>
        /// Invokes the specified parameter.
        /// </summary>
        /// <param name="parameter">The parameter to the action. If the Action does not require a parameter, the parameter may be set to a null reference.</param>
        protected override void Invoke(object parameter)
        {
            if (this.Command != null)
            {
                RoutedCommand comRouted = this.Command as RoutedCommand;
                if (comRouted != null)
                {
                    // Is RoutedCommand
                    comRouted.Execute(this.CommandParameter, this.CommandTarget);
                }
                else
                {
                    // Is NOT RoutedCommand
                    this.Command.Execute((this.CommandParameter != null) ? this.CommandParameter : parameter);
                }
            }
        }

        #endregion

        #region Event Handlers

        #region DP Event Handlers

        /// <summary>
        /// Called when [command changed].
        /// </summary>
        /// <param name="dependencyObject">The dependency object.</param>
        /// <param name="e">The <see cref="System.Windows.DependencyPropertyChangedEventArgs"/> instance containing the event data.</param>
        private static void OnCommandChanged(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs e)
        {
            ((CommandAction)dependencyObject).OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue);
        }

        #endregion

        /// <summary>
        /// Called when [can execute changed].
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.EventArgs"/> instance containing the event data.</param>
        private void OnCanExecuteChanged(object sender, EventArgs e)
        {
            this.UpdateCanExecute();
        }

        #endregion

        #region Helper functions

        /// <summary>
        /// Called when [command changed].
        /// </summary>
        /// <param name="comOld">The COM old.</param>
        /// <param name="comNew">The COM new.</param>
        private void OnCommandChanged(ICommand comOld, ICommand comNew)
        {
            if (comOld != null)
                this.UnhookCommandCanExecuteChangedEventHandler(comOld);
            if (comNew != null)
                this.HookupCommandCanExecuteChangedEventHandler(comNew);
        }

        /// <summary>
        /// Hookups the command can execute changed event handler.
        /// </summary>
        /// <param name="command">The command.</param>
        private void HookupCommandCanExecuteChangedEventHandler(ICommand command)
        {
            this.canExecuteChanged = new EventHandler(this.OnCanExecuteChanged);
            command.CanExecuteChanged += this.canExecuteChanged;
            this.UpdateCanExecute();
        }

        /// <summary>
        /// Unhooks the command can execute changed event handler.
        /// </summary>
        /// <param name="command">The command.</param>
        private void UnhookCommandCanExecuteChangedEventHandler(ICommand command)
        {
            command.CanExecuteChanged -= this.canExecuteChanged;
            this.UpdateCanExecute();
        }

        /// <summary>
        /// Updates the can execute.
        /// </summary>
        private void UpdateCanExecute()
        {
            if (this.Command != null)
            {
                RoutedCommand comRouted = this.Command as RoutedCommand;
                if (comRouted != null)
                {
                    // Is RoutedCommand
                    this.IsEnabled = comRouted.CanExecute(this.CommandParameter, this.CommandTarget);
                }
                else
                {
                    // Is NOT RoutedCommand
                    this.IsEnabled = this.Command.CanExecute(this.CommandParameter);
                }
                if (this.Target != null && this.SyncOwnerIsEnabled)
                    this.Target.IsEnabled = IsEnabled;
            }
        }

        #endregion
    }
}
